﻿using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using System;
using System.Configuration;
using System.Linq;
using System.Reflection;

namespace nMongo
{
    /// <summary>
    /// Represents a engine for connect and realize MongoDB operations
    /// </summary>
    public class MongoEngine
    {
        #region Properties
        protected string _connectionString { get; set; }
        protected MongoServer _server { get; set; }

        /// <summary>
        /// Connection string of the current database.
        /// </summary>
        public string ConnectionString
        {
            get { return _connectionString; }
        }

        /// <summary>
        /// Actual database object.
        /// </summary>
        public MongoDatabase Database { get; protected set; }

        /// <summary>
        /// Determines whether the nMongo persistency control is enabled.
        /// </summary>
        public MongoPersistencyOptions PersistencyControlEnabled { get; set; }
        #endregion

        #region Factories
        public MongoEngine()
            : this(MongoEngine.BuildConnectionFromAppConfig().ConnectionString, MongoEngine.BuildConnectionFromAppConfig().Database.Name)
        {
        }

        public MongoEngine(MongoPersistencyOptions persistencyControlOptions)
            : this(
                MongoEngine.BuildConnectionFromAppConfig().ConnectionString, 
                MongoEngine.BuildConnectionFromAppConfig().Database.Name, 
                persistencyControlOptions)
        {
        }

        /// <param name="connectionString">Connection string of the MongoDB database.</param>
        /// <param name="databaseName">Database name of the MongoDB database.</param>
        public MongoEngine(string connectionString, string databaseName)
            : this(connectionString, databaseName, MongoPersistencyOptions.Disabled)
        {
        }

        /// <param name="connectionString">Connection string of the MongoDB database.</param>
        /// <param name="databaseName">Database name of the MongoDB database.</param>
        /// <param name="persistencyControlOptions">Determines which nMongo persistency control type will be enabled.</param>
        public MongoEngine(string connectionString, string databaseName, MongoPersistencyOptions persistencyControlOptions)
        {
            BuildConnection(connectionString, databaseName);
            PersistencyControlEnabled = persistencyControlOptions;
        }

        /// <summary>
        /// Builds a connection in the current engine object.
        /// </summary>
        /// <param name="connectionString">Connection string of the MongoDB database.</param>
        /// <param name="databaseName">Database name of the MongoDB database.</param>
        public void BuildConnection(string connectionString, string databaseName)
        {
            _connectionString = connectionString;
            MongoClient client = new MongoClient(_connectionString);
            this._server = client.GetServer();
            this.Database = this._server.GetDatabase(databaseName);
        }

        /// <summary>
        /// Builds a connection in the current engine object with a configuration settings manager.
        /// </summary>
        /// <returns>(MongoEngine): An enine built with the configuration settings.</returns>
        /// <remarks>
        /// The configuration settings file must have a <see cref="System.Configuration.ConnectionStringSettings"/> section, with a connection string named with the "-mongouri" suffix.
        /// </remarks>
        public static MongoEngine BuildConnectionFromAppConfig()
        {
            ConnectionStringSettings cnstring = ConfigurationManager.ConnectionStrings
                        .OfType<ConnectionStringSettings>()
                        .FirstOrDefault(x => x.ProviderName.Equals(typeof(MongoEngine).Namespace));

            if (cnstring != null)
                return new MongoEngine(cnstring.ConnectionString, cnstring.Name.ToLower().Replace("-mongouri", string.Empty));

            throw new MongoConnectionException("The automatic connection configuration was not detected in ConfigurationManager.");
        }
        #endregion

        #region Methods
        /// <summary>
        /// Retrieves a collection from the database.
        /// </summary>
        /// <typeparam name="T">Type of the serialized MongoDB collection.</typeparam>
        public MongoCollection<T> GetCollection<T>() where T : class
        {
            string collectionName;
            object[] attributes = typeof(T).GetCustomAttributes(typeof(MongoCollectionAttribute), false);
            if (attributes.Length == 0 || attributes.Count(x => x is MongoCollectionAttribute) == 0)
            {
                collectionName = typeof(T).Name;
            }
            else
            {
                collectionName = ((MongoCollectionAttribute)attributes.First(x => x is MongoCollectionAttribute)).CollectionName;
            }
            return this.Database.GetCollection<T>(collectionName);
        }

        public MongoCollection GetCollection(string collectionName)
        {
            return this.Database.GetCollection(collectionName);
        }

        public MongoCollection<T> GetMortalCollection<T>() where T : class
        {
            string collectionName;
            object[] attributes = typeof(T).GetCustomAttributes(typeof(MongoMortalCollectionAttribute), false);
            if (attributes.Length == 0 || attributes.Count(x => x is MongoMortalCollectionAttribute) == 0)
            {
                collectionName = GetCollection<T>().Name + "_Graveyard";
            }
            else
            {
                collectionName = ((MongoCollectionAttribute)attributes.First(x => x is MongoMortalCollectionAttribute)).CollectionName;
            }
            return this.Database.GetCollection<T>(collectionName);
        }

        /// <summary>
        /// Informs the database that will exist a list of operation requests.
        /// </summary>
        /// <returns>(IDisposable): you may use it inside an "using" clause.</returns>
        public IDisposable RequestStart()
        {
            return this.Database.RequestStart();
        }

        /// <summary>
        /// Informs the database that will exist a list of operation requests.
        /// </summary>
        /// <param name="readPreference">Read preference of the current request list.</param>
        /// <returns>(IDisposable): you may use it inside an "using" clause.</returns>
        public IDisposable RequestStart(ReadPreference readPreference)
        {
            return this.Database.RequestStart(readPreference);
        }

        
        #endregion
    }
}
